import processing.core.*;
import processing.opengl.*;
import traer.physics.*;
import java.awt.*;
import java.util.Random;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Iterator;

public class HMusic extends PApplet {

    //////////////////////////////
    // APPLET-RELATED CONSTANTS //
    //////////////////////////////
    public static final int WIDTH = 1280;
    public static final int HEIGHT = 800;

    ///////////////////////////////
    // PHYSICS-RELATED CONSTANTS //
    ///////////////////////////////
    public static final float ELASTICITY = 0.9f;
    public static final float MAX_VEL = 6.0f;

    // Moon constants
    public static final float MOON_ATTRACTION_DIST = 200;
    public static final float MOON_ATTRACTION_STRENGTH = 1000;
    public static final float MOON_MASS = 0.5f;

    // Planet constants
    public static final float PLANET_ATTRACTION_DIST = 500;
    public static final float PLANET_ATTRACTION_STRENGTH = 100;
    public static final float PLANET_MASS = 10.0f;

    // Solar constants
    public static final float SOLAR_MASS = 25.0f;

    // non-physics, simulation-related constants
    public static final int NUM_PLANETS = 7;
    public static final int NUM_MOONS = 10;
    public static final int TRIANGLE_THRESH = 10000;	// Greatest allowable triangle area
    public static final int MIN_TRIANGLE_AGE = 10;	// Prevent "flashing triangles"
    public static int TRAIL_ERASE_ALPHA = 100;
    private static boolean attractionOn;
    
    private ParticleSystem ps;
    private Particle mouseParticle;
    private Point traveler;				// Point representing the traveler's position
    private LinkedList<HTriangle> tris;			// The triangles that form between the above

    private ArrayList<MoonParticle> moons;
    private ArrayList<PlanetParticle> planets;		// Slightly larger particles than "dust"
    private SolarParticle sun;				// Wandering particle at the center of all

    Random rand;

    public void setup() {
	size(WIDTH, HEIGHT, OPENGL);
	smooth();
	
	ps = new ParticleSystem();
	ps.setIntegrator(ParticleSystem.MODIFIED_EULER);
	ps.setGravity(0.0f);

	mouseParticle = ps.makeParticle();
	mouseParticle.makeFixed();

	rand = new Random();
	tris = new LinkedList<HTriangle>();
	
	planets = new ArrayList<PlanetParticle>();
	moons = new ArrayList<MoonParticle>();
	sun = new SolarParticle ( this, ps );

	// Add planets to the sun
	for (int i=0; i<NUM_PLANETS; i++) planets.add( sun.addPlanet() );
	// Add moons to each of the planets
	for ( PlanetParticle planet : planets )
	    for (int i=0; i<NUM_MOONS; i++)
		moons.add(planet.addMoon());
		
	attractionOn = true;

    }

    /**
     * resetGravity()
     *
     * Set gravity with a new randomized magnitude and direction.
     * */
    private void resetGravity () {
	ps.setGravity(rand.nextFloat() * 0.5f - 0.25f,	// X
		      rand.nextFloat() * 0.5f - 0.25f,	// Y
		      0.0f);				// Z
    }

    public int getWidth() {
	return WIDTH;
    }

    public int getHeight() {
	return HEIGHT;
    }

    public void setAttraction(boolean att) {
	attractionOn = att;
	for ( PlanetParticle planet : planets )
	    planet.attractionOn( att );
	sun.attractionOn( att );
    }

    /**
     * triangulateParticles()
     *
     * draw triangles between close particles in the particle system
     *
     **/
    private void triangulateParticles() {
	// Step 1: Loop through the triangles and remove ones that are no longer needed
	Iterator<HTriangle> ti = tris.iterator();
	while (ti.hasNext()) {
	    HTriangle t_curr = ti.next();
	    if (t_curr.size() > TRIANGLE_THRESH && t_curr.getAge() > MIN_TRIANGLE_AGE)
		ti.remove();
	}

	// Step 2: Collect all the moons together
	for (int i = 0; i<moons.size(); i++) {
	    MoonParticle point1 = moons.get(i);
	    ArrayList<MoonParticle> otherMoons = new ArrayList<MoonParticle>();
	    
	    // Find other close moons
	    for (int j = 0; j<moons.size() && otherMoons.size() < 2; j++) {
		if (j == i) continue;
		MoonParticle point2 = moons.get(j);
		if (Math.hypot((point1.getX() - point2.getX()),
			       (point1.getY() - point2.getY())) <= Math.sqrt(TRIANGLE_THRESH*2))
		    otherMoons.add(point2);

	    }

	    if (otherMoons.size() == 2) {
		HTriangle tt = new HTriangle(this, point1,
						   otherMoons.get(0),
						   otherMoons.get(1));

		// See if the triangle already exists.
		// TODO: This seems jank
		boolean skip = false;
		for (HTriangle tri : tris) // You would think this is the same as "contains". Guess what? It isn't. (WTF?)
		    if (tri.equals(tt))
			skip = true;
		if (skip) continue;
		
		tt.setColor(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255));
		tris.add(tt);
	    }
	
	}
    }
    
    private void updateTraveler () {
	// Update traveler position
	traveler = new Point( mouseX, mouseY );

	// Highlight any triangles that contain the traveler
	for (HTriangle tri : tris) {
	    if ( tri.containsPoint( traveler ) )
		tri.setHighlighted( true );
	    else
		tri.setHighlighted( false );
	}
    }

    /**
     * LISTENERS
     * ==========
     * 
     **/

    public void keyPressed() {
	switch (keyCode) {
	case 32:	// Spacebar
	    setAttraction(! attractionOn);
	    break;
	case 9:		// Tab key
	    for ( PlanetParticle planet : planets )
		moons.add( planet.addMoon() );
	    break;
	case 192:	// Tilde
	    resetGravity();
	    break;
	default:
	    System.out.println("The key was: "+keyCode);
	    break;
	}
    }

    /**
     * draw() loop
     * ============
     * 
     **/

    public void draw() {
	// Perform physics-related operations
	//updateMousePosition();
	ps.tick();

	// Clear the background
	background(0);

	// Apply lighting
	directionalLight(126, 126, 126, 0, 0, -1);
	ambientLight(102, 102, 102);

	// ==================================================
	// // this all for the background
	// strokeWeight(0);
	// if (mousePressed)
	//     TRAIL_ERASE_ALPHA = 20;
	// else
	//     TRAIL_ERASE_ALPHA = 120;

	//	fill(0, 0, 0, TRAIL_ERASE_ALPHA);

	// rect(0, 0, WIDTH, HEIGHT);
	// ==================================================

	// build/destroy triangles
	triangulateParticles();

	// Based on traveler position, highlight HTriangles.
	updateTraveler();

	// draw stuff
	sun.render();

	for ( PlanetParticle planet : planets )
	    planet.render();
	for (HTriangle tt : tris)
	    tt.render();
    }

    private void updateMousePosition() {
	mouseParticle.position().set( mouseX, mouseY, 0);
    }

    
   
    /////////////////////////////////////
    // UTILITY / CONVENIENCE FUNCTIONS //
    /////////////////////////////////////

    public void debug(String message) {
	System.err.println(message);
    }
}